import { LinkedList, LinkedListNode } from "../common/linked_list";
import { Command, Location } from "../protocol/token.service";
import { addStartSplitor, addEndSplitor, addLeftSplitor, addRightplitor, getStartSplitor, getLeftSplitor, getRightSplitor } from "../server/element-data";
import { AddElementLog, ElementLog, FocusElementLog, RemoveElementLog } from "../server/element-log";
import { StructElement, RootElement, SchemaElement, MainElement, SchemaNextLineElement, SchemaSplitorElement, isMainElement, InputElement, withLog } from "../server/schema-element";

const moveToInputElement = (commands: Command[], element: InputElement) => {
    const location = getElementLocation(element);
    commands.push({ command: 'move', toLocation: location, offset: 0 });
}

/**
 * 生成移动到元素之前的命令，并添加到命令列表中
 * @param commands 命令列表
 * @param element 元素
 */
export const moveToBeforeElement = (commands: Command[], element: SchemaElement, node: LinkedListNode<SchemaElement>) => {
    // 获取前一个元素
    let offset = 0;
    let currentNode = node.prev;
    while (currentNode) {
        if (isMainElement(currentNode.value)) {
            break;
        }
        offset++;
        currentNode = currentNode.prev;
    }
    if (currentNode) {
        const location = getElementLocation(currentNode.value as MainElement);
        commands.push({
            command: 'move',
            toLocation: location,
            offset: offset + getRightSplitor(currentNode.value).length
        });
        return;
    }

    const parentElement = element.parentElement!;
    if (parentElement.type === 'root') {
        commands.push({ command: 'move', toLocation: [], offset: 0, moveToContext: true });
    } else {
        const toLocation = getElementLocation(parentElement);
        commands.push({ command: 'move', toLocation, offset: getStartSplitor(parentElement).length + offset, moveToContext: true });
    }
}

const addElementLogCommandList = (commands: Command[], log: AddElementLog) => {
    if (log.element.type === 'nextLine') {
        moveToBeforeElement(commands, log.element, log.node);
        commands.push({ command: 'nextLine', indent: (log.element as SchemaNextLineElement).indent });
        return;
    }
    if (log.element.type === 'splitor') {
        moveToBeforeElement(commands, log.element, log.node);
        const splitor = log.element as SchemaSplitorElement;
        commands.push({ command: 'createSplit', text: splitor.splitor, leftMargin: 0, rightMargin: 0 });
        return;
    }

    // splitor的模式，可以分成以下3种:
    // 1. 元素内部的splitor，这种一般是固定的模式
    // 2. 因为这个元素的添加，导致前面一个元素后需要添加一个splitor，splitor属于前一个元素
    // 3. 创建这个元素后，后面一定会跟一个元素，这两个元素中间需要一个splitor，splitor属于当前创建的元素
    // 4. 因为这个元素的删除，splitor已经没什么作用，需要删除splitor，这个逻辑较为复杂，可以认为需要删除的就是属于当前元素的splitor
    // 5. 元素需要记录属于此元素的splitor
    const element = log.element as MainElement;
    if (element.type === 'value') {
        moveToBeforeElement(commands, element, log.node);
        const typeElement = findTypeElement(element);
        if (typeElement.typeName === 'string') {
            addLeftSplitor(element, '"');
            addRightplitor(element, '"');

            commands.push({ command: 'createSplit', text: '"', leftMargin: 0, rightMargin: 0 });
            commands.push({ command: 'createInput', inputValue: element.value, key: element.index! });
            commands.push({ command: 'createSplit', text: '"', leftMargin: 0, rightMargin: 0 });
        } else {
            commands.push({ command: 'createInput', inputValue: element.value, key: element.index! });
        }
    } else if (element.type === 'type') {
        moveToBeforeElement(commands, element, log.node);
        commands.push({ command: 'createInput', inputValue: element.typeName, key: element.index! });
    } else if (element.type === 'key') {
        moveToBeforeElement(commands, element, log.node);
        commands.push({ command: 'createInput', inputValue: element.key, key: element.index! });
        commands.push({ command: 'createSplit', text: ':', leftMargin: 0, rightMargin: 2 });
        addRightplitor(element, ':');
    } else if (element.type === 'schemaArray') {
        moveToBeforeElement(commands, element, log.node);
        addStartSplitor(element, '[');
        addEndSplitor(element, ']');

        commands.push({ command: 'startContext', label: '', key: element.index! });
        commands.push({ command: 'createSplit', text: '[', leftMargin: 0, rightMargin: 0 });
        commands.push({ command: 'createSplit', text: ']', leftMargin: 0, rightMargin: 0 });
        commands.push({ command: 'endContext' });
    } else if (element.type === 'schemaObject') {
        moveToBeforeElement(commands, element, log.node);
        addStartSplitor(element, '{');
        addEndSplitor(element, '}');

        commands.push({ command: 'startContext', label: '', key: element.index! });
        commands.push({ command: 'createSplit', text: '{', leftMargin: 0, rightMargin: 0 });
        commands.push({ command: 'createSplit', text: '}', leftMargin: 0, rightMargin: 0 });
        commands.push({ command: 'endContext' });
    }
}

const addRemoveLogCommandList = (commands: Command[], log: RemoveElementLog) => {
    commands.push.apply(commands, log.commands);
    const element = log.currentNode.value;
    let start = getLeftSplitor(element).length;
    let end = getRightSplitor(element).length;
    // range的start和end都是闭区间
    // start为1表示从当前位置（当前element之前）的下一个元素开始删除
    commands.push({ command: 'delete', range: { start: 1, end: start + end + 1 } });
}

/**
 * 日志转换为命令列表
 * @param logs 日志列表
 * @returns 命令列表
 */
export const logsToCommandList = (logs: ElementLog[]): Command[] => {
    const commands: Command[] = [];
    for (let i = 0; i < logs.length; i++) {
        const log = logs[i];
        if (log instanceof AddElementLog) {
            addElementLogCommandList(commands, log);
        } else if (log instanceof RemoveElementLog) {
            addRemoveLogCommandList(commands, log);
        } else if (log instanceof FocusElementLog) {
            moveToInputElement(commands, log.element);
        }
    }
    return commands;
}

/**
 * 将elements转换为命令列表
 * @param elements 元素列表
 * @returns 命令列表
 */
export const elementToCommandList = (elements: LinkedList<SchemaElement>): Array<Command> => {
    const logs: Array<ElementLog> = [];
    elementToLogs(elements, logs);
    return logsToCommandList(logs);
}

/**
 * 将元素转换为日志并且添加到logs列表中
 * @param elements 元素列表
 * @param logs 日志
 */
export const elementToLogs = (elements: LinkedList<SchemaElement>, logs: Array<ElementLog>) => {
    let currentNode = elements.getHead();
    while (currentNode) {
        const element = currentNode.value;
        logs.push(new AddElementLog(element, currentNode));
        if (element.type === 'schemaArray' || element.type === 'schemaObject') {
            const children = (element as ContextElement).children;
            elementToLogs(children, logs);
        }
        currentNode = currentNode.next;
    }
}

export const appendElementLog = (
    logs: ElementLog[],
    element: MainElement,
    parent: StructElement,
    node?: LinkedListNode<SchemaElement>
) => {
    return withLog(parent, logs, () => {
        return parent.insertChild(element, node);
    });
}

/**
 * 获取上一个元素
 * @param element 元素
 * @returns 上一个元素
 */
export const getPrevElement = (element: MainElement): MainElement | null => {
    if (!element.index) {
        throw new Error(`element not in element tree, element: ${stringifyElement(element)}`);
    }

    const node = getElementNode(element);
    let currentNode = node.prev;
    while (currentNode) {
        if (isMainElement(currentNode.value)) {
            break;
        }
        currentNode = currentNode.prev;
    }
    return currentNode ? currentNode.value as MainElement : null;
}

export const getTailElement = (element: ContextElement): MainElement | null => {
    let currentNode = element.children.getTail();
    while (currentNode) {
        if (isMainElement(currentNode.value)) {
            break;
        }
        currentNode = currentNode.prev;
    }
    return currentNode ? currentNode.value as MainElement : null;
}

/**
 * 获取下一个元素
 * @param element 元素
 * @returns 下一个元素
 */
export const getNextElement = (element: MainElement): MainElement | null => {
    if (!element.index) {
        throw new Error(`find next element failed, element index is undefined`);
    }

    const node = getElementNode(element);
    let currentNode = node.next;
    while (currentNode) {
        if (isMainElement(currentNode.value)) {
            break;
        }
        currentNode = currentNode.next;
    }
    return currentNode ? currentNode.value as MainElement : null;
}

/**
 * 根据节点获取下一个元素
 * @param node 节点
 * @returns 下一个元素
 */
export const getNextElementByNode = (node: LinkedListNode<SchemaElement>): MainElement | null => {
    let currentNode = node.next;
    while (currentNode) {
        if (isMainElement(currentNode.value)) {
            break;
        }
        currentNode = currentNode.next;
    }
    return currentNode ? currentNode.value as MainElement : null;
}

export const isFirstMainElement = (parent: StructElement, node: LinkedListNode<SchemaElement>) => {
    let current = parent.children.getHead();
    while (current) {
        if (current === node) {
            return true;
        } else if (isMainElement(current.value)) {
            return false;
        }
        current = current.next;
    }
    return false;
}

export const isLastMainElement = (parent: StructElement, node: LinkedListNode<SchemaElement>) => {
    let current = parent.children.getTail();
    while (current) {
        if (current === node) {
            return true;
        } else if (isMainElement(current.value)) {
            return false;
        }
        current = current.prev;
    }
    return false;
}

/**
 * 包含的子元素的长度
 * @param parent 父元素
 * @returns 长度
 */
export const mainElementLength = (parent: StructElement): number => {
    let count = 0;
    let current = parent.children.getHead();
    while (current) {
        if (isMainElement(current.value)) {
            count++;
        }
        current = current.next;
    }
    return count;
}

export const onlyOneChildMainElement = (parent: StructElement): boolean => {
    let count = 0;
    let current = parent.children.getHead();
    while (current) {
        if (isMainElement(current.value)) {
            count++;
            if (count > 1) {
                return false;
            }
        }
        current = current.next;
    }
    return true;
}

/**
 * 下一个元素
 * @param element 
 * @param types 
 * @returns 
 */
export const nextElementTypeMatch = (element: MainElement, types: Array<MainElement['type']>): boolean => {
    const nextElement = getNextElement(element);
    if (!nextElement) {
        return false;
    }
    return types.includes(nextElement.type);
}

/**
 * 根据位置查找节点
 */
export const findNodeByLocation = (rootElement: RootElement, location: Array<number>): LinkedListNode<SchemaElement> => {
    let children = rootElement.children;
    let node: LinkedListNode<SchemaElement> | null = null;

    for (let i = 0; i < location.length; i++) {
        const loc = location[i];
        const currentNode = children.getNode(loc);
        if (!currentNode) {
            throw new Error(`Node not found at index ${loc}`);
        }
        node = currentNode;
        const element = currentNode.value;
        if (element.type === 'schemaObject' || element.type === 'schemaArray') {
            children = (element as SchemaObjectElement | SchemaArrayElement).children;
        } else if (i < location.length - 1) {
            throw new Error(`Element type ${element.type} do not have children`);
        }
    }

    if (!node) {
        throw new Error('Node not found');
    }
    return node;
}

/**
 * 获取元素的在element tree中的位置
 * @param element 元素 
 * @returns 位置，数字组成的数组
 */
export const getElementLocation = (element: MainElement): Location => {
    const location: Array<number> = [];
    let currentElement: MainElement | undefined = element;
    while (currentElement) {
        if (currentElement.index === undefined) {
            throw new Error('The element is not in element tree, elelment: ' + stringifyElement(currentElement));
        }
        location.unshift(currentElement.index);
        if ((currentElement.parentElement!).type === 'root') {
            break;
        }
        currentElement = currentElement.parentElement as MainElement | undefined;
    }
    return location;
}

/**
 * 打印元素
 * @param element 元素 
 */
export const stringifyElement = (element: SchemaElement): string => {
    return JSON.stringify(element, (key, value) => {
        // 打印父元素的location
        if (key === 'parentElement') {
            if (value.type === 'schemaObject' || value.type === 'schemaArray') {
                return getElementLocation(value);
            }
            return [];
        }

        // 打印子元素的数量
        if (key === 'children') {
            return value.length || 0;
        }
        return value;
    }, 2);
}

/**
 * 获取元素在父元素中的节点
 * @param element 元素
 * @returns 节点
 */
export const getElementNode = (element: MainElement): LinkedListNode<SchemaElement> => {
    if (!element.index) {
        throw new Error(`element not in element tree, element: ${stringifyElement(element)}`);
    }
    const node = (element.parentElement!).children.getNode(element.index);
    if (!node) {
        throw new Error(`can't find element in parentElement.children, element: ${stringifyElement(element)}`);
    }
    return node;
}

/**
 * 获取值元素的类型元素
 */
export const findTypeElement = (element: MainElement): SchemaTypeElement => {
    if (!element.index) {
        throw new Error(`element not in element tree, element: ${stringifyElement(element)}`);
    }

    const node = getElementNode(element);
    let currentNode = node.prev;
    while (currentNode) {
        if (currentNode.value.type === 'type') {
            return currentNode.value as SchemaTypeElement;
        }
        if (isMainElement(currentNode.value)) {
            throw new Error(`get type of value element failed, prev element is not a type element!`);
        }
        currentNode = currentNode.prev;
    }
    throw new Error(`get type of value element failed, prev element is null!`);
}

/**
 * 两个location是否相等
 * @param location1 location1
 * @param location2 location2
 * @returns 是否相等
 */
export const locationEq = (location1: Location, location2: Location): boolean => {
    return location1.length === location2.length && location1.every((val, index) => val === location2[index]);
}
